package com.dong6662.spring.bean;

import com.dong6662.spring.aop.*;
import com.dong6662.spring.web.mvc.Controller;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class BeanFactory {
    //bean容器
    private static Map<Class<?>, Object> beans = new ConcurrentHashMap<>();

    private static Set<Class<?>> beansHaveAutoWiredField = Collections.synchronizedSet(new HashSet<>());

    /**
     * 初始化 Bean 工厂,初始化 AOP，这里使用了 JDK 动态代理。Bean工厂第一次初始化后，使用代理类的对象来覆盖 Bean 工厂中的对应对象。
     * 上面这句话是原项目中写的，对后面这句“Bean工厂第一次初始化后，使用代理类的对象来覆盖 Bean 工厂中的对应对象” 有疑问。感觉好像并不是这么做的。待我再仔细看看
     *
     * 做三件事：1.将@Component、@Controller注解的类添加到Bean容器中；2.将@Aspect注解的类添加到bean容器中，并实现@before和@after代理；3.@Autowired注入
     * @param classes
     */
    public static void initBean(List<Class<?>> classes) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //根据以前编程的记忆，这里仍然是浅拷贝，只是换了个指针指向这些classes对象。
        List<Class<?>> classesToCreate = new ArrayList<>(classes);

        //被@Aspect注解的切面类
        List<Class<?>> aspectClasses = new ArrayList<>();

        //过滤出aspectClasses
        for (Class<?> cls:classesToCreate){
            if (cls.isAnnotationPresent(Aspect.class)){
                aspectClasses.add(cls);
            } else {
                createBean(cls);
            }
        }

        //通过上面的循环就可以找出有Aspect注解的类了，现在就需要实现AOP
        resolveAOP(aspectClasses);

        //上面调用createBean()后会保存一些类，这些类的一些field有被@AutoWired注解，这些类在等AOP代理类生成并注入到bean容器中后，
        // 需要将这些代理对象重新注入到相应field中，也就是用代理类替换一下
        for (Class<?> clazz:beansHaveAutoWiredField)
            createBean(clazz);
    }

    /**
     *
     * @param clazz
     */
    private static void createBean(Class<?> clazz) throws InstantiationException, IllegalAccessException {
        // 只处理@Component和@Controller注解的类
        if(!clazz.isAnnotationPresent(Controller.class)
                &&!clazz.isAnnotationPresent(Component.class))
            return;

        // 初始化这个bean
        Object bean = clazz.newInstance();//思考这个方法被废弃的原因（用JDK8的时候好像还没有被废弃，到了11就被废弃了），其实这个方法的文档中给出了答案只不过没做怎么看懂...

        // 下面的这一个大的for循环的任务是进行AutoWired注入
        // 遍历类中所有定义的属性，如果属性带有 @AutoWired 注解，则需要注入对应依赖。
        // 这里不能用getFields()，因为看了文档发现这个方法只返回public的属性，但大部分情况下被@AutoWired注解的属性都不会申明为public
        for(Field field: clazz.getDeclaredFields()){
            if (!field.isAnnotationPresent(AutoWired.class)) continue;

            // TODO: 2021/5/15 这个地方完全没看懂，这个clazz怕是会多次重复add到这个Set里面去吧，因为可能不只一个属性被注入了
            // 将需要注入其他 Bean 的类保存起来，因为等 AOP 代理类生成之后，需要更新它们
            BeanFactory.beansHaveAutoWiredField.add(clazz);

            // 接下来就要从bean容器中拿到相应的bean注入到这个field中了
            Class<?> fieldType = field.getType();
            // 这个地方容易被忽略，因为getDeclaredFields()返回的不只是public属性，所以需要调用这个方法来镇压访问控制
            field.setAccessible(true);
            // 如果这个fieldType是一个接口，就需要单独处理一下:需要先找到这个接口的实现类，然后才能注入这个实现类。
            // 为什么要单独处理呢？因为bean容器(即beans)中的key是value的类型本身，无法体现多态
            if(fieldType.isInterface()){
                for (Class<?> key:BeanFactory.beans.keySet()){
                    // class1.isAssignableFrom(class2) = true 代表class2是class1类型，可分配class2对象给class
                    if(fieldType.isAssignableFrom(key)){
                        fieldType=key;
                        break;
                    }
                }
            }
            // 注入！
            field.set(bean,getBean(fieldType));
        }
        // TODO: 2021/5/15 这里可能AutoWired注入失败，例如存在循环依赖，或者bean工厂中根本不存在，目前暂时先不处理
        beans.put(clazz,bean);
    }

    private static void resolveAOP(List<Class<?>> aspectClasses) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //目前感觉这一句其实可以省略？
        if (aspectClasses.size()==0)
            return;

        for (Class<?> aspectClass:aspectClasses) {
            //因为最后要调用DynamicProxy的createProxy()，所以每一个for循环就是为了获取这些参数的，这样说的话这么长一截for循环代码看起来也没那么复杂了
            Object target = null;
            Object aspect;
            Method before = null;
            Method after = null;
            String pointcutMethod = null;
            String targetMethod = null;

            aspect = aspectClass.newInstance();

            // 假设@Pointcut注解在RapAspect类的rapPoint()方法上，value=com.caozhihu.demo.Rapper.rap()可以获取到的信息有：
            // 切入点方法pointcutMethod为rapPoint()，目标对象target为com.caozhihu.demo.Rapper；目标方法targetMethod为rap()
            for (Method method: aspectClass.getDeclaredMethods()){
                //首先得找到有@Pointcut注解的方法，不然没得玩
                if (!method.isAnnotationPresent(Pointcut.class)) continue;
                String pointCut = method.getAnnotation(Pointcut.class).value();//com.caozhihu.demo.Rapper.rap()
                String targetClassName = pointCut.substring(0,pointCut.lastIndexOf('.'));
                Class<?> targetClass = Thread.currentThread().getContextClassLoader().loadClass(targetClassName);//com.caozhihu.demo.Rapper
                //这里就拿到了target！
                target = targetClass.newInstance();//com.caozhihu.demo.Rapper实例
                targetMethod = pointCut.substring(pointCut.lastIndexOf('.'+1),pointCut.lastIndexOf('('));//rap，没有括号的哦！
                pointcutMethod = method.getName();//rapPoint，也是没有括号
                break;//加这个break应该没问题嘛，反正一个切面类假定只有一个PointCut。
            }
            // 原文中说，设定每一个切面类(Aspect)，最多只有一个切点(Pointcut)、一个前置以及一个后置处理器，所以我们也必需先处理 pointcut，再解析before和after方法。
            // 我本来想直接将获取目标方法和切点的过程和获取before、after的都写在上面的一个for循环中，但是发现得首先获取到pointcut，后续的before和after才有变量用，不信你看：

            // TODO: 2021/5/16 这里我觉得应该加一条判断：如果没有找到织入点(Pointcut) 那就直接抛出一个异常：没有设置PointCutException

            for (Method method: aspectClass.getDeclaredMethods()){
                if(method.isAnnotationPresent(Before.class)){
                    String value = method.getAnnotation(Before.class).value();//这个变量描述的是pointcutMethod，即rapPoint()
                    value = value.substring(0,value.lastIndexOf('('));
                    if (value.equals(pointcutMethod))
                        before = method;
                } else if (method.isAnnotationPresent(After.class)){
                    String value = method.getAnnotation(After.class).value();
                    value = value.substring(0,value.lastIndexOf('('));
                    if(value.equals(pointcutMethod))
                        after = method;
                }
            }
            Object proxy = new DynamicProxy().createProxy(target,before,after,aspect,targetMethod);
            // BeanFactory.beans.put(aspectClass,proxy); 这是我自己制造的错误，put()中的key应该是目标对象target所属的类
            BeanFactory.beans.put(target.getClass(),proxy);
        }

    }

    public static Object getBean(Class<?> clazz){
        // TODO: 2021/5/15 原项目直接采用beans.get(clazz)，但是我不确定是否一个属性
        return BeanFactory.beans.get(clazz);
    }
}
